//  BitWrk - A Bitcoin-friendly, anonymous marketplace for computing power
//  Copyright (C) 2013-2018 Jonas Eschenburg <jonas@bitwrk.net>
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package remotesync

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"github.com/indyjo/cafs"
	"github.com/indyjo/cafs/chunking"
	"io"
	"net/http"
)

// Interface FlushWriter acts like an io.Writer with an additional Flush method.
type FlushWriter interface {
	io.Writer
	Flush()
}

// Struct SimpleFlushWriter implements FlushWriter using a Writer and a Flusher.
type SimpleFlushWriter struct {
	W io.Writer
	F http.Flusher
}

func (s SimpleFlushWriter) Write(p []byte) (n int, err error) {
	return s.W.Write(p)
}

func (s SimpleFlushWriter) Flush() {
	s.F.Flush()
}

// An implementation of FlushWriter whose Flush() function is a nop.
type NopFlushWriter struct {
	W io.Writer
}

func (f NopFlushWriter) Write(p []byte) (n int, err error) {
	return f.W.Write(p)
}

func (f NopFlushWriter) Flush() {
}

// The key pertaining to the SHA256 of an empty string is used to represent placeholders
// for empty slots generated by shuffled transmissions.
var emptyKey = *cafs.MustParseKey("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")

// Type ChunkInfo contains a chunk's hash and size.
type ChunkInfo struct {
	Key  cafs.SKey
	Size int
}

var emptyChunkInfo = ChunkInfo{emptyKey, 0}

func readChunkLength(r *bufio.Reader) (int64, error) {
	if l, err := binary.ReadVarint(r); err != nil {
		return 0, err
	} else if l < 0 || l > chunking.MaxChunkSize {
		return 0, fmt.Errorf("Illegal chunk length: %v", l)
	} else {
		return l, nil
	}
}

func writeVarint(w io.Writer, value int64) error {
	var buf [binary.MaxVarintLen64]byte
	_, err := w.Write(buf[:binary.PutVarint(buf[:], value)])
	return err
}

type bitWriter struct {
	w   FlushWriter
	n   int
	buf [1]byte
}

func newBitWriter(writer FlushWriter) *bitWriter {
	return &bitWriter{w: writer}
}

func (w *bitWriter) WriteBit(b bool) (err error) {
	if b {
		w.buf[0] = (w.buf[0] << 1) | 1
	} else {
		w.buf[0] = w.buf[0] << 1
	}
	w.n++
	if w.n == 8 {
		_, err = w.w.Write(w.buf[:])
		if err == nil {
			w.w.Flush()
		}
		w.n = 0
	}
	return
}

func (w *bitWriter) Flush() (err error) {
	for err == nil && w.n != 0 {
		err = w.WriteBit(false)
	}
	return
}

type bitReader struct {
	r io.ByteReader
	n uint
	b byte
}

func newBitReader(r io.ByteReader) *bitReader {
	return &bitReader{r: r, n: 0, b: 0}
}

func (r *bitReader) ReadBit() (bit bool, err error) {
	if r.n == 8 {
		r.n = 0
	}
	if r.n == 0 {
		r.b, err = r.r.ReadByte()
		if err != nil {
			return
		}
	}
	n := r.n
	r.n++
	bit = 0 != (0x80 & (r.b << n))
	return
}

// Function readChunk reads a single chunk worth of data from stream `r` into a new
// file on FileStorage `s`.
// The expected encoding is (varint, data...).
func readChunk(s cafs.FileStorage, r *bufio.Reader, info string) (cafs.File, error) {
	var length int64
	if n, err := readChunkLength(r); err != nil {
		return nil, err
	} else {
		length = n
	}
	tempChunk := s.Create(info)
	defer tempChunk.Dispose()
	if _, err := io.CopyN(tempChunk, r, length); err != nil {
		return nil, err
	}
	if err := tempChunk.Close(); err != nil {
		return nil, err
	}
	return tempChunk.File(), nil
}
